Repository navigation
Conversation
The RTL8812CU could not air a single 40 MHz frame on most channels while the same card does HT40 under mainline rtw88. Two faults, both gated on the 8822C so the 8822E's descriptor and DIG stay byte-identical: - The inject descriptor carried DISDATAFB=0 (the Jaguar1 mirror). With USE_RATE=1 the MAC's per-frame rate-fallback lookup consults the fw's per-MACID entry, which an injecting MACID lacks; on the 8822C that wedges the first 40 MHz frame and the TX FIFO fills. The device now passes dis_data_fb=1 on the C die (the vendor's fixed-rate path does). - On a quiet channel the receiver parks below IGI 0x24 in a gain state where the secondary 20 MHz never reads idle, so the MAC holds every 40 MHz frame; busy channels self-heal because received frames re-run the AGC. DIG_MIN_8822C_WIDE 0x26 is pushed into the phydm runtime after every tune (Init/InitWrite/SetMonitorChannel/FastRetune/ FastSetBandwidth) at 40/80 MHz and honoured by every DIG pass. Costs ~6 dB of idle-channel RX sensitivity at those widths on the CU. Measured on 144(-)/52(+)/149(+)/36(+): ~20.5k frames per 50 s run, 0 failures, all received at MCS3 bw 40; HT20 unchanged. Record: docs/experiments/8822c-ht40-tx-stall.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PbfYcAbwocHjWbXn9q8tzB
… sent The floor costs ~6 dB of idle-channel RX sensitivity, which a device that only receives at 40 MHz — a CU ground station, whose own control frames are 20 MHz — pays for nothing. Arm it from build_tx_block on the first >= 40 MHz descriptor (one _reg_mu take, the FastRetune precedent), keep it across tunes while the width stays wide, drop it at a narrow width. Bench, ch 144 silent, no knobs: HT40 TX 9091 frames / 0 failed with IGI 0x26 after arming; 20 MHz frames into the 40 MHz tune 9928 / 0 at IGI 0x20; RX-only at 40 MHz stays at 0x20. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PbfYcAbwocHjWbXn9q8tzB
…cost as an estimate Review follow-up to the 8822C HT40 inject fix. - A 40 MHz frame into a 20 MHz tune armed the floor and apply_igi_floor_locked disarmed it again at the narrow width, so every such frame re-took _reg_mu on the send path (txdemo hits it with DEVOURER_TX_RATE=.../40 and no DEVOURER_HOP_BW). Arming now requires the tune to be >= 40 MHz too. - Stop() clears the arm, so a later Init on the same device starts without the floor. - The RX-sensitivity cost was quoted as "~6 dB". It is arithmetic from the 1 dB IGI step, not a measurement, and 6 dB holds only over the DFS floor; on a non-DFS channel (36 and 149 in the result table) it is 4-8 dB over the coverage window. Header, experiment doc and CLAUDE.md now say so. - src/jaguar3/CLAUDE.md keeps the fact and points at the headers and the experiment doc instead of restating them. No hardware re-run: the arming change only removes a narrow-tune case that never applied the floor anyway. ctest 81/81. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Five 30 s cells on silent 144(-)/149(+), both adapters local: - CU HT40: 0 failed, witness 99.6 % at MCS3 bw:1, floor armed at 0x26. - 40 MHz frames into a 20 MHz tune leave the floor unarmed (0x20). - HT20 control: unchanged. - 8822E HT40: 0 failed, 99.1 % at bw:1, IGI 0x20, as is the RX-only CU witness. Near-field single runs. The send rate is gap-limited, so this does not measure the removed per-frame lock. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PR Summary by QodoFix 8822C 40 MHz frame injection stalls
AI Description
Diagram
High-Level Assessment
Files changed (7)
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Code Review by Qodo
1.
|
…ssion-scoped floor arm - DISDATAFB=1 was written on every 8822C frame, which silently turned the fw retry ladder (OpenIPC#362/OpenIPC#371, RetryFallback::Default) off at 20 MHz too. It now covers 40/80 MHz frames only. Measured on the CU, unACKed unicast MCS3 limit 8: - 20 MHz: MCS3 x4 > MCS2 > 6M restored, 1223/1224. - 40 MHz: pinned at MCS3, 1208/1209. - 20 MHz frames with DISDATAFB=0 on a 40 MHz tune: 14163 / 0 failed, so the wedge keys on frame width. DeviceConfig.h's RetryFallback note says the 8822C's wide frames have no ladder at either setting. - The floor arm is now "this session has sent a wide frame" (_wide_tx_seen): - Set once, published after the floor is applied, cleared by Init, InitWrite and Stop. - The floor follows the tuned width, read under _reg_mu on both sides. - A retune racing the first wide frame can no longer skip it, and a re-Init no longer inherits it. - A 40 MHz frame on a 20 MHz tune still takes the lock once per session. - Dropping the floor clamps IGI back into the channel's DIG window at once, instead of leaving 0x26 until the next tick. Keeping it at 0 touches no register, so the 8822E path is unchanged. Not exercised on air: the drop clamp and the race. ctest 78/78. HT40 CU and EU and HT20 cells re-run, 0 failed (experiment doc). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ssion-scoped floor arm - DISDATAFB=1 was written on every 8822C frame, which silently turned the fw retry ladder (OpenIPC#362/OpenIPC#371, RetryFallback::Default) off at 20 MHz too. It now covers 40/80 MHz frames only. Measured on the CU, unACKed unicast MCS3 limit 8: - 20 MHz: MCS3 x4 > MCS2 > 6M restored, 1223/1224. - 40 MHz: pinned at MCS3, 1208/1209. - 20 MHz frames with DISDATAFB=0 on a 40 MHz tune: 14163 / 0 failed, so the wedge keys on frame width. DeviceConfig.h's RetryFallback note says the 8822C's wide frames have no ladder at either setting. - The floor arm is now "this session has sent a wide frame" (_wide_tx_seen): - Set once, published after the floor is applied, cleared by Init, InitWrite and Stop. - The floor follows the tuned width, read under _reg_mu on both sides. - A retune racing the first wide frame can no longer skip it, and a re-Init no longer inherits it. - A 40 MHz frame on a 20 MHz tune still takes the lock once per session. - Dropping the floor clamps IGI back into the channel's DIG window at once, instead of leaving 0x26 until the next tick. Keeping it at 0 touches no register, so the 8822E path is unchanged. Not exercised on air: the drop clamp and the race. ctest 78/78. HT40 CU and EU and HT20 cells re-run, 0 failed (experiment doc). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…only, session-scoped floor) into the fork Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Folds the original table and both re-test sections into one: GS and local benches in one table, superseded intermediate rows dropped, the retry ladder and the limits stated once. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
josephnef
left a comment
There was a problem hiding this comment.
Re-ran the PR's cells on a second bench and the mechanism holds, but the two things the PR commits to — the 0x26 constant and the C-die-only gate — do not survive it. Request changes on those two; the code shape itself is fine.
Bench: LB-LINK WDN1300H (RTL8812CU, 0bda:c812, chip-id 0x13 cut 3) and a LB-LINK BL-M8812EU2 module (RTL8812EU, 0bda:a81a, rfe 21), near-field, no rtw88 loaded. Witness = rxdemo on the other adapter at the same channel/width, counting rx.txhit with bw:1. TX = txdemo, DEVOURER_TX_QOS_DATA=1 DEVOURER_TX_PAYLOAD_BYTES=1000, MCS3/40/LDPC, 2 ms gap, 20 s per cell, single run each. 144(-) and 100(+) were silent (8 s witness scan at 40 MHz: 0 frames). PR binary = c59a3a3; "both dies" rows = the same tree with the C8822C test removed from apply_igi_floor_locked and the arm in build_tx_block (DISDATAFB still C-only).
| cell | TX → witness | ch | floor | submitted | failed | witness bw:1 |
|---|---|---|---|---|---|---|
| master | CU → EU | 144(-) | – | 257 | 8 | 0 |
| PR | CU → EU | 144(-) | 0x26 (armed, logged) | 257 | 8 | 0 |
PR, DEVOURER_DIS_CCA=1 |
CU → EU | 144(-) | 0x26 | 257 | 8 | 0 |
| PR | CU → EU | 100(+) | 0x26 | 308 | 8 | 51 |
| PR | CU → EU | 36(+) (busy) | 0x26 | 7842 | 0 | 7727 |
| PR + 8822BU flooding MCS0/20 at 50 fps | CU → EU | 144(-) | 0x26 | 7321 | 8 | 7064 |
| PR, floor 0x2c | CU → EU | 144(-) | 0x2c | 1752 | 8 | 1495 |
| PR, floor 0x35 | CU → EU | 144(-) | 0x35 | 7851 | 0 | 7801 |
| PR, floor 0x35 | CU → EU | 100(+) | 0x35 | 7837 | 0 | 7787 |
| PR (unchanged E path) | EU → CU | 144(-) | none (IGI 0x20) | 257 | 8 | 0 |
| PR (unchanged E path) | EU → CU | 100(+) | none | 269 | 8 | 12 |
| PR + flooder | EU → CU | 144(-) | none | 4811 | 343 | 4095 |
| PR, floor on both dies, 0x2c | EU → CU | 144(-) | 0x2c | 7858 | 6 | 7794 |
| PR, floor on both dies, 0x35 | EU → CU | 144(-) | 0x35 | 7787 | 4 | 7725 |
| PR, floor on both dies, 0x35 | EU → CU | 100(+) | 0x35 | 7897 | 3 | 7844 |
| PR | CU → EU | 144(-), MCS3/20 into the 40 tune | 0x20 | 7857 | 0 | 7807 (bw:0) |
In the flooder row the 8 failures are the last 8 sends, after the flooder's timeout ended ~2 s before the DUT's: the hang resumes the moment the channel goes quiet, which is the PR's model exactly.
1. DIG_MIN_8822C_WIDE = 0x26 is a one-unit calibration. On this CU the armed 0x26 floor (confirmed in the log) changes nothing against master on both silent channels; 0x2c airs ~1.5k frames then wedges; 0x35 clears it. Your unit's threshold sat at 0x22/0x24, this one's sits between 0x2c and 0x35. A hard constant one step above one unit's edge will ship as "40 MHz still dead" on the next unit. Either take the floor high enough to carry the units measured so far (with the sensitivity cost restated: 0x35 is 21 steps over the DFS floor, not 6), or expose it as a DeviceConfig field with that default, and in both cases say in the header and the experiment doc that the edge is per-unit and which units were measured. The vendor phydm has no bandwidth-raised bound to lean on here (phydm_dig_abs_boundary_decision pins DFS at DIG_MIN_DFS at every width), so this really is our own number.
2. The E die has the same stall on this bench. The PR's "8822E does HT40 unchanged" is true on your EU and false on this one: 257/8/0 on silent 144(-) and 100(+), by the unchanged E path, same shape as the CU, partially relieved by the flooder and cleared by the same floor at 0x2c and 0x35. So the quiet-channel half must not be gated on C8822C; arm and apply it on both Jaguar3 dies (DISDATAFB can stay C-only — the EU flew with DISDATAFB=0 and the floor). That also removes the "8822E's DIG is byte-identical" sentences from src/jaguar3/CLAUDE.md, DeviceConfig.h and the experiment doc.
3. Same stall with the demo's default 148-byte management frame (no DEVOURER_TX_QOS_DATA): 953 submitted / 8 failed / 0 witnessed at 40 MHz on both dies, master and PR alike, on silent 144(-). Not a separate fault as far as I can tell — just noting the frame shape doesn't matter.
Smaller points, no re-test needed:
FastRetune's fast path returns beforeapply_igi_floor_locked; fine today because the lean hop never rewrites 0x1d70 and the floor value doesn't depend on DFS-ness, but worth one line in the comment there so the next person doesn't "fix" it.is_dfs_channel:channel > 14 &&is redundant with>= 52.- The
Init/InitWriteapply_igi_floor_lockedcalls can only ever clear a floor left in_phydmby a previous session (the flag was just reset). Say so in the comment; right now it reads as if they could arm one. DeviceConfig.hline 290 runs well past 80 columns.- The 80 MHz cell here (CU → EU, 149(+), VHT1SS_MCS3/80, floor 0x26) was partial too: 470 / 10 / 209. Same open item as the PR says, not new.
Every row above is one run, so treat the 0x2c and the two partial rows as indicative; the 0-vs-7.8k rows are not inside any noise band.
The problem
The RTL8812CU (8822C) could not air a single 40 MHz injected frame on most 5 GHz channels, though the same card does HT40 under mainline rtw88. On 144(-), 36(+) and 149(+), bulk-OUT accepted 257 frames and then failed with rc=-7, and nothing went on air. At the abort the TX FIFO was full (
LQ 64/0 PUB 1745/19): the MAC never completed the first 40 MHz frame. HT20 was unaffected.There were two independent faults, and fixing either alone is not enough.
The change
Both parts apply only to the 8822C. The 8822E's descriptor and DIG are byte-identical to before.
fill_data_tx_desc_8822c, newdis_data_fbargument).DEVOURER_TX_RETRY_FALLBACKsetting, andDeviceConfig.hsays so.DIG_MIN_8822C_WIDE 0x26,PhydmRuntimeJaguar3::set_igi_floor).Stop()clear the arm._reg_muby every tune (Init / InitWrite / SetMonitorChannel / FastRetune / FastSetBandwidth). It is on at 40/80 MHz. At a narrower width it drops, with IGI clamped straight back into the normal DIG window._reg_muonce per session and never on the steady-state send path. The flag is published only after the floor is applied, so neither a racing retune nor a concurrent sender can skip it.Record, including the dead ends (firmware RA_INFO in every layout, firmware version, IQK, TX power, CCA, AGC lower bound):
docs/experiments/8822c-ht40-tx-stall.md.Measured
txdemo on the CU, with an 8812EU witness on rxdemo at the same channel and width. 1 kB QoS-data frames, MCS3/40/LDPC, 50 s per run, no knobs. "Aired" means CRC-ok frames the witness decoded with
bw:1.Before the change, the same 40 MHz runs aired nothing.
How each half was isolated:
The counterpart: the floor costs idle-channel RX sensitivity at 40/80 MHz on a CU that transmits wide. That cost is estimated from the 1 dB IGI step, not measured: 6 dB over the DFS floor 0x20, and 4–8 dB over the 0x1e..0x22 coverage window on a non-DFS channel. Each row is a single 50 s run. The effect size (0 vs ~20k) is far outside probe-to-probe noise, but the 36(+) row's lower witness count (96.3% vs ~99.5% elsewhere) was not repeated.
Re-test on the final code (both adapters local, near-field, silent channels confirmed by an 8 s scan beforehand, 30 s per cell):
After the review (f87521b), same bench, silent 144:
Retry ladder,
tests/retry_ladder_probe.shshape: unACKed unicast, MCS3, limit 8.Headless: ctest full 78/78, without Jaguar3 78/78, 8822C-only 69/69.
Not covered
🤖 Generated with Claude Code